package aldap

import (
	"crypto/tls"
	"fmt"
	"log"

	"gopkg.in/ldap.v2"
)

// CLDAP ldap verify
// ZA: ldap check user information
type CLDAP struct {
	Con *ldap.Conn
}

// NewLDAP new ldap connection
func NewLDAP(url string, port int, userName string, password string) *CLDAP {
	l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", url, port))
	if err != nil {
		log.Fatalln("conn", err)
	}

	err = l.StartTLS(&tls.Config{InsecureSkipVerify: true})
	if err != nil {
		log.Fatalln("TLS", err)
	}

	err = l.Bind(userName, password)
	if err != nil {
		log.Fatalln("bind", err)
	}

	return &CLDAP{Con: l}
}

//queryInfo query user information
func (c *CLDAP) queryInfo(baseDN string, userEmail string) (cur *ldap.SearchResult, err error) {
	sql := ldap.NewSearchRequest(baseDN,
		ldap.ScopeWholeSubtree,
		ldap.NeverDerefAliases,
		0,
		0,
		false,
		fmt.Sprintf("(&(objectClass=organizationalPerson)(uid=%s))", userEmail),
		[]string{"cn", "uid"}, nil)

	cur, err = c.Con.Search(sql)
	if err != nil {
		return nil, err
	}

	if len(cur.Entries) == 0 {
		err = fmt.Errorf("%s does not exist", userEmail)
		return nil, err
	}

	if len(cur.Entries) > 1 {
		err = fmt.Errorf("exist multiple %s", userEmail)
		return nil, err
	}

	return cur, nil
}

// CheckPassword check user login
func (c *CLDAP) CheckPassword(baseDN string, userEmail string, userPassword string) error {
	cur, err := c.queryInfo(baseDN, userEmail)
	if err != nil {
		return err
	}

	userdn := cur.Entries[0].DN
	err = c.Con.Bind(userdn, userPassword)
	if err != nil {
		return err
	}

	return nil
}

// GetCnValue  get cn of LDAP by user email
func (c *CLDAP) GetCnValue(baseDN string, userEmail string) (string, error) {
	cur, err := c.queryInfo(baseDN, userEmail)
	if err != nil {
		return "", err
	}
	cn := cur.Entries[0].GetAttributeValue("cn")
	return cn, nil
}

// GetEntries get entries of LDAP
func (c *CLDAP) GetEntries(baseDN string) (entries []*ldap.Entry, err error) {
	sql := ldap.NewSearchRequest(baseDN,
		ldap.ScopeWholeSubtree,
		ldap.NeverDerefAliases,
		0,
		0,
		false,
		"(&(objectClass=organizationalPerson))",
		[]string{"cn", "uid"}, nil)

	cur, err := c.Con.Search(sql)
	if err != nil {
		return nil, err
	}

	if len(cur.Entries) == 0 {
		err = fmt.Errorf("nobody")
		return nil, err
	}

	return cur.Entries, nil
}

// Close Release resources
func (c *CLDAP) Close() {
	c.Con.Close()
}
